﻿using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
using Model.DocumentDB.Resources;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace Model.DocumentDB
{
    /// <summary>Represents a collection within a DB account.</summary>
    public class CollectionViewModel : DocumentClientViewModelBase<DatabaseViewModel>, IRefreshable
    {
        private readonly DocumentCollection _collection;

        /// <summary>Constructor.</summary>
        /// <param name="client">Client API of the account.</param>
        /// <param name="collection">Collection object.</param>
        /// <param name="parent">Parent of the model.</param>
        internal CollectionViewModel(DocumentClient client, DocumentCollection collection, DatabaseViewModel parent)
            : base(client, parent)
        {
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }

            _collection = collection;

            DocumentContent = TextResource.DocumentSample;
            QueryContent = TextResource.QuerySample + Name + " AS c";
            DocumentFolder = new FolderViewModel<DocumentViewModel, CollectionViewModel>(
                this,
                "Documents",
                true,
                DocumentViewModel.GetLazyCollection(Client, _collection.DocumentsLink, this));
            Artefacts = new FolderViewModel[]
            {
                DocumentFolder,
                new FolderViewModel<int,CollectionViewModel>(this,"Sproc", false, LoadableCollection<int>.CreateEmpty()),
                new FolderViewModel<int,CollectionViewModel>(this,"Trigger", false, LoadableCollection<int>.CreateEmpty()),
                new FolderViewModel<int,CollectionViewModel>(this,"UDF", false, LoadableCollection<int>.CreateEmpty())
            }.ToList();
            QueryResults = new LoadableCollection<QueryResultViewModel>(ExecuteQueryAsync);
        }

        /// <summary>Returns a lazy-loading collection of collections of a given database.</summary>
        /// <param name="client">Client API of the account.</param>
        /// <param name="collectionsLink">Link to collections.</param>
        /// <param name="parent">Parent of the model.</param>
        /// <returns>Collections.</returns>
        internal static LoadableCollection<CollectionViewModel> GetLazyCollection(
            DocumentClient client,
            string collectionsLink,
            DatabaseViewModel parent)
        {
            if (client == null)
            {
                throw new ArgumentNullException("client");
            }
            if (string.IsNullOrWhiteSpace(collectionsLink))
            {
                throw new ArgumentNullException("collectionsLink");
            }

            return new LoadableCollection<CollectionViewModel>(async () =>
            {
                var collections = await client.ReadDocumentCollectionFeedAsync(collectionsLink);
                var models = from collection in collections
                             let model = new CollectionViewModel(client, collection, parent)
                             orderby model.Name
                             select model;

                return models;
            });
        }

        /// <summary>Documents of the collection.</summary>
        public FolderViewModel<DocumentViewModel, CollectionViewModel> DocumentFolder { get; private set; }

        /// <summary>Artefacts of a collection.</summary>
        public List<FolderViewModel> Artefacts { get; private set; }

        /// <summary>Collection name.</summary>
        public string Name
        {
            get { return _collection.Id; }
        }

        /// <summary>Collection link.</summary>
        public string SelfLink
        {
            get { return _collection.SelfLink; }
        }

        /// <summary>Content of a document.</summary>
        /// <remarks>Meant to allow variation of a document to be re-entered.</remarks>
        public string DocumentContent { get; set; }

        /// <summary>Content of a query.</summary>
        /// <remarks>Meant to keep the query around while jumping from an artefact to another.</remarks>
        public string QueryContent { get; set; }

        /// <summary>Results of a query.</summary>
        public LoadableCollection<QueryResultViewModel> QueryResults { get; private set; }

        /// <summary>Asynchronously creates a collection under the DB.</summary>
        /// <param name="content">JSON content (textual representation).</param>
        /// <returns>Asynchronous completion.</returns>
        public async Task CreateDocumentAsync(string content)
        {
            var json = JObject.Parse(content);
            var document = await Client.CreateDocumentAsync(_collection.DocumentsLink, json);
            var viewModel = new DocumentViewModel(Client, document, this);

            DocumentFolder.FolderContent.Add(viewModel);
        }

        Task IRefreshable.RefreshAsync()
        {
            return DocumentFolder.FolderContent.RefreshAsync();
        }

        Task IRefreshable.EnsureLoadedAsync()
        {
            return DocumentFolder.FolderContent.EnsureLoadedAsync();
        }

        private async Task<IEnumerable<QueryResultViewModel>> ExecuteQueryAsync()
        {
            var options = new FeedOptions { EnableScanInQuery = true };
            var query = Client.CreateDocumentQuery(_collection.DocumentsLink, QueryContent, options);
            var documentQuery = query as IDocumentQuery<dynamic>;
            var results = new List<QueryResultViewModel>();

            while (documentQuery.HasMoreResults)
            {
                var subResults = await documentQuery.ExecuteNextAsync();

                foreach (var text in subResults.Cast<object>())
                {
                    results.Add(new QueryResultViewModel(results.Count() + 1, text.ToString()));
                }
            }

            return results;
        }

        private string Serialize(JsonSerializable serializable)
        {
            using (var stream = new MemoryStream())
            {
                serializable.SaveTo(stream, SerializationFormattingPolicy.Indented);
                stream.Position = 0;

                using (var reader = new StreamReader(stream))
                {
                    return reader.ReadToEnd();
                }
            }
        }
    }
}